Skip to content

test(pdf): #204 extract Golden Master face->back matching to pure method + 8 tests - #523

Merged
jsboige merged 1 commit into
masterfrom
test/204-face-to-back-matching-contract
Jun 17, 2026
Merged

test(pdf): #204 extract Golden Master face->back matching to pure method + 8 tests#523
jsboige merged 1 commit into
masterfrom
test/204-face-to-back-matching-contract

Conversation

@jsboige

@jsboige jsboige commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Extract the Golden Master (commit 0087f0ec) face→back name-matching contract from AssembleCurrentCardImages (ImageFileGenerator.cs) into a pure, deterministic ResolveCardBack method and pin it with 8 unit tests.

Lane #204 (test/contract extractions, cont. po-2024) — same spirit/output-neutral pattern as #512 (page-grid geometry) and #521 (alternate-face-and-back).

The contract

When a card set ships several distinct back arts, the harvest must pair EACH face with the correct back by name, not randomly. ResolveCardBack encodes:

  • No backs → face ships alone (null back).
  • Single back → shared by every face.
  • Multiple backs → the back whose (lower-cased) key is contained in the (lower-cased) face key wins, longest matching key first (tie-break via OrderByDescending(Length)).
  • No name matches → falls back to the first available back.

Why it's a fragile contract

A regression here silently pairs the WRONG back art behind a face while leaving page count, geometry, and card ordering all correct — the defect surfaces only by inspecting which back sits behind which printed card. Previously inlined inside AssembleCurrentCardImages with zero unit coverage.

Output-neutral extraction

The call site assembles the exact same CardImages Front/Back pair as before. The Logger.LogWarning diagnostics and targetList.Add stay at the call site; ResolveCardBack surfaces the branch taken via out bool hadNoAvailableBack / out bool usedFallback so the caller emits its existing warnings without duplicating the branch logic. Verified by build + full suite.

Subtlety pinned by the tests

Back dictionary keys carry a leading hyphen: GenerateBacks (lines 124-130) strips to the suffix after the last hyphen, keeping the hyphen — e.g. harvested "scenarii-01-histoire" → key "-histoire". So the Contains match looks for "-histoire", not the bare token "histoire". This was not obvious from a read of AssembleCurrentCardImages alone; the tests now lock it in (e.g. a face "Face_histoire_titre" with bare histoire would not match a "-histoire" key).

Conceptual complement of #521

Together they pin the full back contract: which back, then where it prints.

Tests (8)

Area Test
No-back / single-back NoBacks_ReturnsNull_AndFlagsNoAvailableBack, SingleBack_IsSharedByEveryFace
Name-matching MultipleBacks_PicksBackWhoseKeyIsContainedInFaceKey, NameMatching_IsCaseInsensitive, MultipleBacks_PicksUnrelatedBackForUnrelatedFace
Longest-key tie-break LongestMatchingKeyWins_WhenMultipleKeysMatch, ShorterOnlyMatch_StillPicked_WhenNoLongerMatch
Fallback NoNameMatch_FallsBackToFirstBack_AndFlagsFallback

CI

  • 359 passed / 0 failed / 5 skipped (baseline 351 + 8 new).
  • Files: ImageFileGenerator.cs (extraction) + FaceToBackMatchingContractTests.cs (new tests).
  • No CSV touched, no RowsetNb/rscount change, no .github/workflows or rules edit.

🤖 Worker po-2024 — gate-safe, ready for ai-01 review/merge.

…hod + 8 tests

Extract the face->back name-matching contract (Golden Master, 0087f0e)
from AssembleCurrentCardImages (ImageFileGenerator.cs) into a pure,
deterministic ResolveCardBack method and pin it with 8 unit tests.

The contract decides which back art pairs behind a face when a card set
ships several distinct backs: the back whose (lower-cased) key is
contained in the (lower-cased) face key wins, with the LONGEST matching
key taken first (tie-break), single-back sets share one back, no-back
sets ship faces alone, and a non-matching face falls back to the first
available back. A regression here silently pairs the WRONG back behind a
face while leaving page count, geometry, and ordering correct.

Extraction is output-neutral: the call site assembles the exact same
CardImages Front/Back pair as before (the Logger warnings and
targetList.Add stay at the call site, surfaced via out flags).

Key subtlety pinned by the tests: back dictionary keys carry a leading
hyphen (GenerateBacks strips to the suffix AFTER the last hyphen, keeping
the hyphen: "scenarii-01-histoire" -> "-histoire"), so the Contains match
looks for "-histoire", not the bare token "histoire". This was not obvious
from a read of AssembleCurrentCardImages alone.

This is the conceptual complement of #521 (PdfAlternateFaceAndBack):
that contract ORDERS fronts/backs back-then-front; this one CHOOSES which
back each face gets.

Suite: 359 passed / 0 failed / 5 skipped (baseline 351 + 8 new).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@clusterManager-Myia

Copy link
Copy Markdown
Collaborator

[NanoClaw]

Good extraction: inlines the face->back name-matching logic from AssembleCurrentCardImages into a pure, deterministic ResolveCardBack method (output-neutral refactoring). 8 xUnit tests using FluentAssertions cover all branches: no-backs, single-back, substring matching, case-insensitivity, unrelated face, longest-key-first tie-break, shorter-only match, and fallback-to-first. The test names are descriptive and the helper Backs() factory keeps fixtures readable. The tie-break behavior (longest matching key wins) is the critical regression case — correctly tested. LGTM.

@jsboige
jsboige merged commit 010ea58 into master Jun 17, 2026
3 checks passed
@jsboige
jsboige deleted the test/204-face-to-back-matching-contract branch June 17, 2026 17:08
jsboige added a commit that referenced this pull request Jun 18, 2026
… method + 7 tests (#529)

Extract the back-name normalization rule (Golden Master, 0087f0e) from
GenerateBacks (ImageFileGenerator.cs) into a pure, deterministic
NormalizeBackKey method and pin it with 7 unit tests.

The rule: given an already-lower-cased back key, if it contains a hyphen,
strip everything BEFORE the last hyphen, keeping that hyphen as a leading
prefix (e.g. "scenarii-01-histoire" -> "-histoire"); otherwise return it
unchanged. The resulting key indexes the back-images dictionary and is what
ResolveCardBack later substring-matches against face keys.

This is the OTHER half of the face->back contract pinned in #523
(ResolveCardBack): that contract chooses which back each face gets; this one
produces the keys matching runs against. A regression here (strip at the
first hyphen, or drop the leading hyphen) silently realigns every back key so
ResolveCardBack falls through to the first-back fallback for every multi-back
card set -- wrong back behind every face, with correct count/geometry/ordering.

Extraction is output-neutral: the image is still loaded/processed with the
FULL lower-cased name; only the dictionary key is normalized. The leading
hyphen is part of the matching contract (a face must contain "-histoire", not
the bare "histoire") -- locked in by the #523 tests and reaffirmed here.

Suite: 366 passed / 0 failed / 5 skipped (baseline 359 + 7 new).

Co-authored-by: Your <your.email@example.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants